package com.github.wxiaoqi.security.crm.core.blockchain.dpos;

import java.math.BigInteger;
import java.time.Instant;

import com.github.wxiaoqi.security.crm.core.blockchain.block.DposBlock;
import com.github.wxiaoqi.security.crm.core.blockchain.transaction.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import com.github.wxiaoqi.security.crm.core.blockchain.block.Block;
import com.github.wxiaoqi.security.crm.core.blockchain.util.ByteUtils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author: bolei
 * @date：2018年9月8日 下午2:56:21
 * @description：授权股权证明机制 根据原力和火钻的排行前2100名地址 分配奖励
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DelegatedProofOfStake {
    private DposBlock block;
    /**
     * 平台编号
     */
    private Long platformId;
    //火源是根据原力和火钻的排行前2100名地址 分配奖励
    //
    //找到各个平台下2100名用户  火钻统称（火币、水币）是完全根据原力值大小挖
    public static DelegatedProofOfStake newDelegatedProofOfStake(DposBlock block, Long platformId) {
        return new DelegatedProofOfStake(block,platformId);
    }

    /**
     * 准备数据
     * <p>
     * 注意：在准备区块数据时，一定要从原始数据类型转化为byte[]，不能直接从字符串进行转换
     *
     * @return
     */
    private byte[] prepareData() {
        byte[] prevBlockHashBytes = {};
        if (StringUtils.isNoneBlank(this.getBlock().getPrevBlockHash())) {
            prevBlockHashBytes = new BigInteger(this.getBlock().getPrevBlockHash(), 16).toByteArray();
        }
        this.getBlock().setHeight(getPreBlockHeight(this.getPlatformId())+1);
        double blockReward = getCurrentReward(getPreBlockHeight(this.getPlatformId()));
        this.getBlock().setReward(blockReward);
        String[] luckAddress = getRewardAddresses(this.getPlatformId());
        DposTransaction transaction = new DposTransaction();
        DposTXOutput[] txs = new DposTXOutput[luckAddress.length];
        //建立交易输入，为空，区块奖励
        /**
         * 区块奖励是否可以这样：在前期商户配置生成火钻数量时配置一定量的奖励币，同时生成一个奖励地址（这个地址只是发放奖励使用）
         * 在创世区块产生时，在创世区块中打包一条交易，交易内容是：发送一定数量的币给奖励地址。这样就可以奖励就包含在了区块中，
         * 这样就可以追踪奖励的分配，也能追踪奖励的剩余。如果追加奖励，则可以向奖励地址进行转账。
         *
         */
        DposTXInput txInput = new DposTXInput();
        for (int i = 0; i < luckAddress.length; i++) {
            DposTXOutput txOutput = DposTXOutput.newDposTXOutput(dispatchReward(blockReward,luckAddress[i]), luckAddress[i]);
            txs[i] = txOutput;
        }
        transaction.setInputs(new DposTXInput[]{txInput});
        transaction.setOutputs(txs);
        transaction.setCreateTime(Instant.now().getEpochSecond());
        transaction.setTxType(TransactionType.Reward.getCode());
        transaction.setTxId(transaction.hash());
        this.getBlock().setRewardTransaction(transaction);
        return ByteUtils.merge(
                prevBlockHashBytes,
                this.getBlock().hashTransaction(),
                ByteUtils.toBytes(this.getBlock().getTimeStamp())
        );
    }


    /**
     * 开始挖矿
     *
     * @return
     */
    public String run() {
        String shaHex = "";
        byte[] data = this.prepareData();
        shaHex = DigestUtils.sha256Hex(data);
        return shaHex;
    }

    /**
     * 获取奖励的地址
     * 根据平台编号从platform_coin_customer中获取原力值排行前200 地址
     * platform_coin_customer中是否要加入原力值 ？
     * @param platformId 平台编号所在的区块链
     * @return 获取奖励的所有地址
     */
    private String[] getRewardAddresses(Long platformId) {
        return new String[]{};
    }

    /**
     * 获取链的高度
     * 从platform_blockchain中获取高度
     * @param platformId 平台编号所在的区块链
     * @return 链高度
     */
    private int getPreBlockHeight(Long platformId) {
        return 0;
    }

    /**
     * 计算区块产出奖励
     * @param chainHeight
     * @return 区块奖励
     */
    private double getCurrentReward(long chainHeight) {
        return (50 / ((chainHeight / 21000)+1));
    }

    /**
     * 分配奖励 根据地址的原力比例对reward进行分配
     *
     * @param reward
     * @param address
     * @return 该地址的奖励
     */
    private int dispatchReward(double reward,String address) {
        return 0;
    }

    public DposBlock getBlock() {
        return block;
    }

    public void setBlock(DposBlock block) {
        this.block = block;
    }

    public Long getPlatformId() {
        return platformId;
    }

    public void setPlatformId(Long platformId) {
        this.platformId = platformId;
    }
}
